package com.ming.sevice.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ming.constant.QueueConstant;
import com.ming.domain.*;
import com.ming.feign.OrderCartFeign;
import com.ming.feign.OrderMemberFeign;
import com.ming.feign.OrderProductFeign;
import com.ming.mapper.OrderItemMapper;
import com.ming.mapper.OrderSettlementMapper;
import com.ming.model.WxMsgModel;
import com.ming.sevice.OrderItemService;
import com.ming.vo.OrderConfirm;
import com.ming.vo.OrderResult;
import com.ming.vo.OrderStatus;
import com.ming.vo.ShopOrderItem;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ming.mapper.OrderMapper;
import com.ming.sevice.OrderService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

/**
 *@Author:何元才
 *@Desc:
 *@data:2021-04-09 15:14
 */
@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService{


    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private OrderItemMapper orderItemMapper;

    @Autowired
    private OrderItemService orderItemService;

    @Autowired
    private OrderMemberFeign orderMemberFeign;

    @Autowired
    private OrderProductFeign orderProductFeign;

    @Autowired
    private OrderCartFeign orderCartFeign;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private OrderSettlementMapper orderSettlementMapper;

    /**
     * 获取用户的订单状态
     *
     * @param userId
     * @return
     */

    @Override
    public OrderStatus getOrderCount(String userId) {
        // 查询待付款的
        Integer unPay = orderMapper.selectCount(new LambdaQueryWrapper<Order>()
                .eq(Order::getUserId, userId)
                .eq(Order::getStatus, 1)
        );
        // 查询待付款的
        Integer payed = orderMapper.selectCount(new LambdaQueryWrapper<Order>()
                .eq(Order::getUserId, userId)
                .eq(Order::getStatus, 2)
        );
        // 查询待收货的
        Integer consignment = orderMapper.selectCount(new LambdaQueryWrapper<Order>()
                .eq(Order::getUserId, userId)
                .eq(Order::getStatus, 3)
        );

        return OrderStatus.builder()
                .unPay(unPay)
                .payed(payed)
                .consignment(consignment)
                .build();
    }

    /**
     * 分页查询用户订单详情
     * 1.分页查询order表
     * 2.拿到orderNumList
     * 3.查询orderItem表  拿到集合
     * 4.循环order表判断组装数据
     * 5.返回
     *
     * @param page
     * @param status
     * @param userId
     * @return
     */
    @Override
    public Page<Order> getOrderPage(Page<Order> page, Integer status, String userId) {
        Page<Order> orderPage = orderMapper.selectPage(page, new LambdaQueryWrapper<Order>()
                .eq(Order::getUserId, userId)
                .eq(status != null && status != 0, Order::getStatus, status)
                .orderByDesc(Order::getCreateTime)
        );
        //1.拿到订单的集合
        List<Order> orderList = orderPage.getRecords();
        if (CollectionUtils.isEmpty(orderList)){
            return orderPage;
        }
        //2.拿到orderNums 订单编号是一对多的关系
        List<String> orderNums = orderList.stream()
                .map(Order::getOrderNumber)
                .collect(Collectors.toList());
        //3.查询orderItem表 拿到订单条目集合
        List<OrderItem> orderItems = orderItemMapper.selectList(new LambdaQueryWrapper<OrderItem>()
                .in(OrderItem::getOrderNumber, orderNums)
        );
        //循环order集合判断组装
        orderList.forEach(order -> {
            //拿到对应订单的条目集合
            List<OrderItem> orderItemList = orderItems.stream()
                    .filter(orderItem -> orderItem.getOrderNumber().equals(order.getOrderNumber()))
                    .collect(Collectors.toList());
            order.setOrderItemDtos(orderItemList);
        });
        return orderPage;
    }

    /**
     * 订单确认接口
     * 看放回值需要什么
     * 1.远程调用拿到用户默认收货地址
     * 2.组装商品详情 判断
     * ---------到底是从购物车进来 需要远程调用购物车 拿到购物车集合
     *---------------远程调用商品 根据skuIds 拿到sku
     * ---------还是从商品进来
     * --------------直接远程调用商品 根据skuIds 拿到sku
     * 3.循环 组装数据
     * 4，计算总金额 运费 减满之类
     * 5.返回
     *
     * @param userId
     * @param orderConfirm
     * @return
     */
    @Override
    public OrderResult orderConfirm(String userId, OrderConfirm orderConfirm) {
        OrderResult orderResult = new OrderResult();
        //1.需要用户的默认收货地址 远程调用
        UserAddr userDefaultAddr = orderMemberFeign.getUserDefaultAddr(userId);
        //不需要判断是否为null
        orderResult.setUserAddr(userDefaultAddr);
        //2.判断是否从购物车进来的
        List<Long> basketIds = orderConfirm.getBasketIds();
        if (CollectionUtils.isEmpty(basketIds)){
            //从商品进来
            productToConfirm(orderResult,orderConfirm);
        } else{
            //从购物车进来
            cartToConfirm(orderResult,basketIds);
        }
        return orderResult;
    }


    /**
     * 从购物车进来确认
     * 1.远程调用购物车 根据ids 拿到购物车集合
     * 2.拿到skuIds
     * 3.远程调用商品拿到skuList
     * 4.循环组装的数据
     * 5.计算总金额
     *
     * @param orderResult
     * @param basketIds
     */
    private void cartToConfirm(OrderResult orderResult, List<Long> basketIds) {
        //远程调用购物车 根据ids 拿到购物车集合
        List<Basket> basketList = orderCartFeign.getCartsByIds(basketIds);
        if (CollectionUtils.isEmpty(basketList)){
            throw new RuntimeException("服务器异常");
        }
        //从购物车中拿到skuIds
        List<Long> skuIds = basketList.stream()
                .map(Basket::getSkuId)
                .collect(Collectors.toList());
        //远程调用商品服务 拿到sku集合
        List<Sku> skuList = orderProductFeign.getSkuListByIds(skuIds);
        if (CollectionUtils.isEmpty(skuList)){
            throw new RuntimeException("服务器异常");
        }
        //创建存放单个商品总价的集合
        List<BigDecimal> totalMoneyList = new ArrayList<>(basketList.size()*2);
        //组装数据 总金额 总数量 运费 创建商家信息集合
        List<ShopOrderItem> shopCartOrders = new ArrayList<>();
        //创建一个商品信息总对象
        ShopOrderItem shopOrderItem = new ShopOrderItem();
        //创建商品体条目集合
        List<OrderItem> items = new ArrayList<>();
        //循环购物车
        basketList.forEach(basket -> {
            //拿到单个商品的数量
            Integer count = basket.getBasketCount();
            Sku sku1 = skuList.stream()
                    .filter(sku -> sku.getSkuId().equals(basket.getSkuId()))
                    .collect(Collectors.toList())
                    .get(0);
            //计算单个商品的金额
            BigDecimal oneMoney = sku1.getPrice().multiply(new BigDecimal(count));
            //把单个金额存放总金额集合中 方便计算
            totalMoneyList.add(oneMoney);
            //创建条目对象
            OrderItem orderItem = new OrderItem();
            //将sku中的属性拷贝到条目中
            BeanUtil.copyProperties(sku1,orderItem,true);
            //将单个商品是数量 存放进去
            orderItem.setProdCount(count);
            //把条目对象添加到条目集合中
            items.add(orderItem);
        });
        //计算总价
        BigDecimal totalMoney = totalMoneyList.stream().reduce(BigDecimal::add).get();
        // 做运费计算
           //比较总费用是否 > 99  即满100 减免 运费
        int i = totalMoney.compareTo(new BigDecimal(99));
        shopOrderItem.setTransfee(new BigDecimal(0));
        orderResult.setTotal(totalMoney);
        orderResult.setActualTotal(totalMoney);
        if ( i <= 0 ){
            //不能包邮
            shopOrderItem.setTransfee(new BigDecimal(6));
            orderResult.setActualTotal(totalMoney.add(new BigDecimal(6)));
        }
        //计算这个订单中总数量 map reduce
        Integer totalCount = basketList.stream()
                .map(Basket::getBasketCount)
                .reduce(Integer::sum)
                .get();
        orderResult.setTotalCount(totalCount);
        //设置条目集合到商家信息对象中
        shopOrderItem.setShopCartItemDiscounts(items);
        // 设置商家信息对象到商家信息集合中
        shopCartOrders.add(shopOrderItem);
        // 设置商家信息集合到返回结果中
        orderResult.setShopCartOrders(shopCartOrders);
    }

    /**
     * 从商品直接进来确认
     * 1.拿到sku
     * 2.远程调用获取sku信息
     * 3.计算价格之类的
     * 4.组装参数和返回值
     *
     * @param orderResult
     * @param orderConfirm
     */
    private void productToConfirm(OrderResult orderResult, OrderConfirm orderConfirm) {
        //从前端拿到一些值
        OrderItem orderItem = orderConfirm.getOrderItem();
        Long skuId = orderItem.getSkuId();
        Integer count = orderItem.getProdCount();
        //远程调用 根据skuId查询sku
        List<Sku> skuList = orderProductFeign.getSkuListByIds(Arrays.asList(skuId));
        if (CollectionUtils.isEmpty(skuList)){
            throw new RuntimeException("服务器维护中");
        }
        Sku sku = skuList.get(0);
        // 组装数据
        // 计算金额
        BigDecimal totalMoney = sku.getPrice().multiply(new BigDecimal(count));
        // 设置运费
        orderResult.setAllTransfee(BigDecimal.ZERO);
        // 设置实际金额
        orderResult.setActualTotal(totalMoney);
        // 设置总金额
        orderResult.setTotal(totalMoney);
        // 设置count
        orderResult.setTotalCount(count);
        // 组装商品详情 创建商家信息集合
        List<ShopOrderItem> shopCartOrders = new ArrayList<>();
        // 创建一个商家信息对象
        ShopOrderItem shopOrderItem = new ShopOrderItem();
        // 给这个商家信息的商品设置运费
        shopOrderItem.setTransfee(new BigDecimal(0));
        // 设置商家满减
        shopOrderItem.setShopReduce(new BigDecimal(0));

        // 运费查询运费的业务  超过99 就包邮  运费6元
        int i = totalMoney.compareTo(new BigDecimal(99));
        if (i <= 0){
            //商品的运费
            shopOrderItem.setTransfee(new BigDecimal(6));
            orderResult.setAllTransfee(new BigDecimal(6));
            //设置实际金额
            orderResult.setActualTotal(totalMoney.add(new BigDecimal(6)));
        }
        // 创建 商品条目集合
        List<OrderItem> shopCartItemDiscounts = new ArrayList<>();
        // 拷贝 商品条目对象 填充数据
        BeanUtil.copyProperties(sku, orderItem, true);
        // 将商品对象添加到商品条目集合中
        shopCartItemDiscounts.add(orderItem);
        // 将商品条目集合设置到 商家信息对象中
        shopOrderItem.setShopCartItemDiscounts(shopCartItemDiscounts);
        // 将商家信息对象放入 商家信息集合中
        shopCartOrders.add(shopOrderItem);
        // 将商家信息集合放入返回对象中
        orderResult.setShopCartOrders(shopCartOrders);

    }

    /**
     * 订单提交 （预订单）
     * 1.生成一个订单号
     * 2.扣减mysql库存
     * 3.扣减es库存
     * 4.写订单表 状态待支付 （预订单）
     * 5.写订单条目表
     * 6.订单结算表  （预结算）
     * 7.写死信队列--- 做一个延迟支付  30min 没有支付 订单就取消    回滚mysql es  修改订单表为失败  修改订单结算表 未结算
     * 8.给用户发一个微信通知
     * 9.做用户加积分，成长值
     * 10.清空购物车
     *
     * 问题
     * 分布式事务问题  解决  LCN  MQ  TCC  seata
     *
     * @param userId
     * @param orderResult
     * @return
     */
    @Override
    public String submit(String userId, OrderResult orderResult) {
        // 1.生成订单号
        String orderNum = createOrderNum();

        // 2.清空购物车 根据skuId 和 userId 清空
        clearCart(userId,orderResult);

        // 3.扣减mysql库存 + - 号判断库存的扣减和回滚
        Map<Long, Integer> prodMap = changeStock(orderResult);

        // 4.扣减es的库存  + -  prod     prodId  count
        changeEsStock(prodMap);


        // 5.写订单表
        Map<String, Object> map = writeOrder(userId,orderNum,orderResult);

        // 6.给用户发通知 用户id  商品名称 金额  订单超时时间
        sendWxMsg(userId,map);

        // 7.将订单写入死信队列  为了延迟支付功能
        writeDeadQueue(orderNum);
        
        // 8.加积分 成长值

        // 返回订单号
        return orderNum;
    }



    /**
     * 订单存放延迟队列
     * 只要创建订单 就往这个里面放消息就可以了
     *
     * @param orderNum
     */
    private void writeDeadQueue(String orderNum) {
        rabbitTemplate.convertAndSend(QueueConstant.PROD_MS_QUEUE, orderNum);
    }

    /**
     * 发微信消息
     *
     * @param userId
     * @param orderResult
     */
    private void sendWxMsg(String userId, Map<String, Object> map) {
        //构建一个微信消息
        WxMsgModel wxMsgModel = new WxMsgModel();
        wxMsgModel.setUrl("https://www.bilibili.com/");
        wxMsgModel.setToUser("o4rU65iDLxh5A-MzPQ25LdKK1ZHk");
        wxMsgModel.setTopColor("#FF0000");
        wxMsgModel.setTemplateId("vZZXhAqQKyzQGAEmJMoxA_Ml0XhEBN9hx-mF48Ly8nw");
        // 组装数据
        HashMap<String,Map<String,String>> data = new HashMap<>(8);
        data.put("time",WxMsgModel.buildMap(LocalDateTime.now().toString(), "#FF0000"));
        data.put("orderName",WxMsgModel.buildMap(map.get("orderName").toString(), "#FF0000"));
        data.put("price", WxMsgModel.buildMap(map.get("price").toString(), "#FF0000"));
        data.put("cancelTime", WxMsgModel.buildMap("30", "#FF0000"));
        wxMsgModel.setData(data);
        // 放入mq
        rabbitTemplate.convertAndSend(QueueConstant.WX_SMS_EX, QueueConstant.WX_SMS_KEY, JSON.toJSONString(wxMsgModel));

    }

    /**
     * 前台传来的数据 特别是敏感数据 不能直接使用
     * 写订单表
     * 1.写order
     * 2.写orderItem表
     * 3.写结算表
     *
     * @param userId
     * @param orderNum
     * @param orderResult
     * @return
     */
    private Map<String, Object> writeOrder(String userId, String orderNum, OrderResult orderResult) {
        Map<String,Object> data = new HashMap<>(8);

        // 写order表 需要总金额 订单名称
        // 写orderItem表
        List<OrderItem> orderItems = orderResult.getShopCartOrders().get(0).getShopCartItemDiscounts();
        // 金额不能直接用 从新拿到sku
        // 拿到skuIds
        List<Long> skuIds = orderItems.stream()
                .map(OrderItem::getSkuId)
                .collect(Collectors.toList());
        // 这个里面的金额是正确的 保证数据在服务器端进行传输
        List<Sku> skuList = orderProductFeign.getSkuListByIds(skuIds);
        StringBuffer stringBuffer = new StringBuffer();
        // 总金额的池子
        List<BigDecimal> totalMoneyList = new ArrayList<>();
        // 循环 orderItems 往里面放一些正确数据
        orderItems.forEach(orderItem -> {
            // 拿到对应的sku
            Sku sku1 = skuList.stream()
                    .filter(sku -> sku.getSkuId().equals(orderItem.getSkuId()))
                    .collect(Collectors.toList())
                    .get(0);
            orderItem.setPrice(sku1.getPrice());
            // 数量 和单个商品的总价
            BigDecimal oneMoney = sku1.getPrice().multiply(new BigDecimal(orderItem.getProdCount()));
            orderItem.setProductTotalAmount(oneMoney);
            totalMoneyList.add(oneMoney);
            orderItem.setRecTime(new Date());
            orderItem.setOrderNumber(orderNum);
            orderItem.setUserId(userId);
            orderItem.setShopId(1L);
            stringBuffer.append(orderItem.getProdName());
            stringBuffer.append(",");
        });
        // 插入数据库
        orderItemService.saveBatch(orderItems);
        String orderName = stringBuffer.toString();
        // 把 ， 去掉 RK KMP
        String realOrderName = orderName.substring(0, orderName.lastIndexOf(","));
        // 构建order表
        Order order = new Order();
        // 订单商品数量
        Integer totalCount = orderItems.stream().map(OrderItem::getProdCount).reduce(Integer::sum).get();
        // 设置数量 金额
        order.setCreateTime(new Date());
        order.setUpdateTime(new Date());
        order.setOrderNumber(orderNum);
        order.setShopId(1L);
        // 设置名称
        order.setProdName(orderName);
        order.setUserId(userId);
        order.setRemarks(orderResult.getRemarks());
        order.setIsPayed(false);
        order.setStatus(1);
        // 总金额 总数量
        BigDecimal totalMoney = totalMoneyList.stream()
                .reduce(BigDecimal::add)
                .get();
        order.setTotal(totalMoney);
        order.setActualTotal(totalMoney);
        // 运费
        int i = totalMoney.compareTo(new BigDecimal(99));
        if (i < 0 ){
            totalMoney = totalMoney.add(new BigDecimal(6));
            order.setTotal(totalMoney);
            order.setActualTotal(totalMoney);
        }
        order.setProductNums(totalCount);
        orderMapper.insert(order);
        // 结算表
        OrderSettlement orderSettlement = new OrderSettlement();
        orderSettlement.setCreateTime(new Date());
        orderSettlement.setOrderNumber(orderNum);
        orderSettlement.setUserId(userId);
        orderSettlement.setIsClearing(0);
        orderSettlement.setPayAmount(totalMoney);
        orderSettlement.setPayStatus(0);
        orderSettlementMapper.insert(orderSettlement);

        // 组装数据库
        data.put("price",totalMoney.toString());
        data.put("orderName",realOrderName);
        return data;
    }

    /**
     * 扣减es库存
     * 快速导入
     *
     * @param orderResult
     */
    private void changeEsStock(Map<Long, Integer> prodMap) {
        //直接往mq里面放
        rabbitTemplate.convertAndSend(QueueConstant.PHONE_SMS_EX,QueueConstant.PROD_CHANGE_KEY, JSON.toJSONString(prodMap));
    }

    /**
     * 修改mysql库存  一对多的关系
     * prod表       1            2
     * sku表    111  112     888  889
     * 修改数量  1     2       2    2
     * <p>
     * 修改prod    1   -3      2   -4
     * prod数据  id  count
     * sku数据   id  count
     *
     * @param orderResult
     * @return
     */
    private Map<Long, Integer> changeStock(OrderResult orderResult) {
        HashMap<String, Map<Long, Integer>> data = new HashMap<>(4);
        // skuIds  prodIds  count
        List<OrderItem> orderItems = orderResult.getShopCartOrders().get(0).getShopCartItemDiscounts();
        //我们把数据收集起来 各自存放 用来发请求
        Map<Long, Integer> prodMap = new HashMap<>();
        Map<Long, Integer> skuMap = new HashMap<>();
        orderItems.forEach(orderItem -> {
            Long prodId = orderItem.getProdId();
            Long skuId = orderItem.getSkuId();
            Integer prodCount = orderItem.getProdCount();
            //因为sku不用 但是prod可能相同
            if (prodMap.containsKey(prodId)){
                // 如果prodMap里面有一个key了  我只需要改value
                prodMap.put(prodId, prodMap.get(prodId) + prodCount);
            } else {
                prodMap.put(prodId,prodCount);
            }
            skuMap.put(skuId,prodCount);
        });
        // 发起远程调用
        data.put("prod", prodMap);
        data.put("sku", skuMap);
        orderProductFeign.changeStock(data);
        return prodMap;
    }

    /**
     * 清空购物车
     * 根据skuIds 和用户id删除购物车
     *
     * @param userId
     * @param orderResult
     */
    private void clearCart(String userId, OrderResult orderResult) {
        //拿到skuIds
        List<OrderItem> orderItems = orderResult.getShopCartOrders().get(0).getShopCartItemDiscounts();
        List<Long> skuIds = orderItems.stream()
                .map(OrderItem::getSkuId)
                .collect(Collectors.toList());
        //远程调用根据条件清空购物车
        orderCartFeign.clearCart(userId,skuIds);
    }


    /**
     * 生成订单号的方法
     * 分布式唯一id的生成
     * 我们单独弄一个生成id的服务 在这里面使用雪花
     * 雪花算法 只能用到2039年
     * 组成 ： 64位
     * 第1位是符号位
     * 接下来的41位 是时间戳 11111111111111111111111111111111111111111
     * 5位机房id 32
     * 5位机器id 32
     * 12位随机数
     *
     * @return
     */
    private String createOrderNum() {
        Snowflake snowflake = IdUtil.createSnowflake(1, 1);
        String orderNum = snowflake.nextIdStr();
        return orderNum;
    }




    /**
     * 修改订单状态 已经支付
     *
     * @param orderNum
     */
    @Override
    @Transactional(rollbackFor = RuntimeException.class)
    public void changeOrderStatus(String orderNum) {
        // 1.order
        Order order = orderMapper.selectOne(new LambdaQueryWrapper<Order>()
                .eq(Order::getOrderNumber, orderNum)
        );
        if (ObjectUtils.isEmpty(order)){
            throw new RuntimeException("订单不存在");
        }
        order.setStatus(2);
        order.setUpdateTime(new Date());
        orderMapper.updateById(order);
        // 2.结算表
        OrderSettlement orderSettlement = orderSettlementMapper.selectOne(new LambdaQueryWrapper<OrderSettlement>()
                .eq(OrderSettlement::getOrderNumber, orderNum)
        );
        orderSettlement.setIsClearing(1);
        orderSettlement.setCreateTime(new Date());
        orderSettlementMapper.updateById(orderSettlement);
    }

}
